// Copyright (c) Microsoft Corporation. 
// Licensed under the MIT license.

#include "StandInMeshActorItem.h"
#include "LevelEditor.h"
#include "StandinActor.h"
#include "StandInsOutliner.h"
#include "ScopedTransaction.h"
#include "TreeItemID.h"
#include "StandinUtilitiesModule.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Engine/StaticMesh.h"

#define LOCTEXT_NAMESPACE "LODActorItem"

StandInsOutliner::FStandInMeshActorItem::FStandInMeshActorItem(AStandInMeshActor* InLODActor)
	: StandInMeshActor(InLODActor), ID(InLODActor)
{
	Type = ITreeItem::TreeItemType::StandInMeshActor;
}


bool StandInsOutliner::FStandInMeshActorItem::CanInteract() const
{
	return true;
}

void StandInsOutliner::FStandInMeshActorItem::GenerateContextMenu(FMenuBuilder& MenuBuilder, SStandInsOutliner& Outliner)
{
	auto SharedOutliner = StaticCastSharedRef<SStandInsOutliner>(Outliner.AsShared());
	if (StandInMeshActor->IsDirty())
	{
		MenuBuilder.AddMenuEntry(LOCTEXT("BuildActorMesh", "Build"), FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(&Outliner, &SStandInsOutliner::BuildStandInMeshActor, false)));
	}
	else
	{
		MenuBuilder.AddMenuEntry(LOCTEXT("RebuildActorMesh", "Rebuild"), FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(&Outliner, &SStandInsOutliner::BuildStandInMeshActor, true)));
	}

	MenuBuilder.AddMenuSeparator();

	MenuBuilder.AddMenuEntry(LOCTEXT("SelectContainedActors", "Select Contained Actors"), FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(&Outliner, &SStandInsOutliner::SelectContainedActors)));

	MenuBuilder.AddMenuEntry(LOCTEXT("RemoveActorAndChildren", "Remove"), FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(&Outliner, &SStandInsOutliner::RemoveStandInMeshActor)));

	MenuBuilder.AddMenuEntry(LOCTEXT("SaveToPackage", "Save to package"), FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(&Outliner, &SStandInsOutliner::SaveToPackage)));

}

FString StandInsOutliner::FStandInMeshActorItem::GetDisplayString() const
{
	if (AStandInMeshActor* ActorPtr = StandInMeshActor.Get())
	{
		ActorPtr->CleanupInvalidSourceActors();
		FString DisplayName = ActorPtr->GetName() + (!ActorPtr->HasValidSubActors() ? FString(" (Not enough mesh components)") : ((ActorPtr->IsDirty()) ? FString(" (Not built)") : FString()));
		return DisplayName;
	}

	return FString("StandinActor");
}

FString StandInsOutliner::FStandInMeshActorItem::GetName() const
{
	if (AStandInMeshActor* ActorPtr = StandInMeshActor.Get())
	{
		return ActorPtr->GetName();
	}
	else
	{
		return FString("");
	}
}

StandInsOutliner::FTreeItemID StandInsOutliner::FStandInMeshActorItem::GetID()
{
	return ID;
}

FSlateColor StandInsOutliner::FStandInMeshActorItem::GetTint() const
{
	AStandInMeshActor* LODActorPtr = StandInMeshActor.Get();
	if (LODActorPtr)
	{
		return LODActorPtr->IsDirty() ? FSlateColor::UseSubduedForeground() : FLinearColor(1.0f, 1.0f, 1.0f);
	}

	return FLinearColor(1.0f, 1.0f, 1.0f);
}

FText StandInsOutliner::FStandInMeshActorItem::GetLevelAsText() const
{
	if (StandInMeshActor.IsValid())
	{
		return FText::FromString(StandInMeshActor->GetSourceActors().Num() ? StandInMeshActor->GetSourceActors()[0]->GetLevel()->GetOuter()->GetName() : TEXT(""));
	}
	else
	{
		return FText::FromString(TEXT("Not available"));
	}
}


/** Returns the number of sub actors in FText form */
FText StandInsOutliner::FStandInMeshActorItem::GetNumberOfSubActorsAsText() const
{
	if (StandInMeshActor.IsValid())
	{
		int32 NumMeshes = StandInMeshActor->GetSourceStaticMeshComponentCount();
		int32 SubActors = StandInMeshActor->GetSourceActors().Num();
		return FText::FromString(NumMeshes > 0 ? FString::Printf(TEXT("%d/%d"), SubActors,NumMeshes) : TEXT("None"));
	}
	else
	{
		return FText::FromString(TEXT("Not available"));
	}
}

/** Returns the number of sub actors in FText form */
FText StandInsOutliner::FStandInMeshActorItem::GetNumTrianglesAsText() const
{
	if (StandInMeshActor.IsValid())
	{
		int32 NumTriangles = StandInMeshActor->GetNumTriangles();
		int32 NumChildrenTriangles = StandInMeshActor->GetNumChildrenTriangles();
		return FText::FromString( FString::Printf(TEXT("%s / %s"), *FText::AsNumber(NumTriangles).ToString(), *FText::AsNumber(NumChildrenTriangles).ToString()) ); // FromString(StandInMeshActor->SubActors.Num() > 0 ? FString::Printf(TEXT("%d"), StandInMeshActor->SubActors.Num()) : TEXT("None"));
	}
	else
	{
		return FText::FromString(TEXT("N/A"));
	}
}

/** Returns the number of sub actors in FText form */
FText StandInsOutliner::FStandInMeshActorItem::GetStandInProcessTypeAsText() const
{
	if (StandInMeshActor.IsValid())
	{
		USimplygonStandinPipeline* Pipeline = StandInMeshActor->GetPipeline();
		if(!Pipeline)
			return FText::FromString(TEXT("N/A"));

		return Pipeline->GetClass()->GetDisplayNameText();
	}

	return FText::FromString(TEXT("N/A"));
}

/** Returns the number of sub actors in FText form */
FText StandInsOutliner::FStandInMeshActorItem::GetLODGroupdAsText() const
{
	if (StandInMeshActor.IsValid())
	{
		TArray<FName> LODGroupNames;
		LODGroupNames.Reset();
		UStaticMesh::GetLODGroups(LODGroupNames);

		FText LODGroupName = FText::FromString(TEXT("None"));
		return LODGroupName;
	}
	else
	{
		return FText::FromString(TEXT("Not available"));
	}
}

void StandInsOutliner::FStandInMeshActorItem::PopulateDragDropPayload(FDragDropPayload& Payload) const
{
	AStandInMeshActor* ActorPtr = StandInMeshActor.Get();
	if (ActorPtr)
	{
		if (!Payload.StandinLODActors)
		{
			Payload.StandinLODActors = TArray<TWeakObjectPtr<AActor>>();
		}
		Payload.StandinLODActors->Add(StandInMeshActor);
	}
}

StandInsOutliner::FDragValidationInfo StandInsOutliner::FStandInMeshActorItem::ValidateDrop(FDragDropPayload& DraggedObjects) const
{
	//NOTE:StandInMeshes Should only have a single parent. so we can invalidate it early
	
	if (Parents.Num() == 1 && Parents[0].IsValid())
	{
		if (Parents[0].Pin()->GetTreeItemType() == ITreeItem::StandInMeshActor)
		{
			return FDragValidationInfo(EStandInsOutlinerActionType::InvalidAction, FStandInsOutlinerDragDropOp::ToolTip_Incompatible, LOCTEXT("CannotAddToStandInMeshActor", "Cannot add to stand-in mesh actor."));
		}
	}
	
	FStandInMeshActorDropTarget Target(StandInMeshActor.Get());
	return Target.ValidateDrop(DraggedObjects);
}

void StandInsOutliner::FStandInMeshActorItem::OnDrop(FDragDropPayload& DraggedObjects, const FDragValidationInfo& ValidationInfo, TSharedRef<SWidget> DroppedOnWidget)
{
	FStandInMeshActorDropTarget Target(StandInMeshActor.Get());
	Target.OnDrop(DraggedObjects, ValidationInfo, DroppedOnWidget);

	// Expand this HLOD actor item
	bIsExpanded = true;
}

void StandInsOutliner::FStandInMeshActorItem::GenerateSourceActorsContextMenu(FMenuBuilder& /*MenuBuilder*/)
{

}

StandInsOutliner::FDragValidationInfo StandInsOutliner::FStandInMeshActorDropTarget::ValidateDrop(FDragDropPayload& DraggedObjects) const
{
	if (DraggedObjects.StaticMeshActors.IsSet() && DraggedObjects.StaticMeshActors->Num() > 0)
	{
		if (DraggedObjects.StaticMeshActors->Num() > 0 && DraggedObjects.StandinLODActors->Num() == 0)
		{
			if (DraggedObjects.bSceneOutliner == false)
			{
				bool bContaining = false;

				// Check if this StaticMesh Actor is not already inside this cluster
				for (int32 ActorIndex = 0; ActorIndex < DraggedObjects.StaticMeshActors->Num(); ++ActorIndex)
				{
					if (StandInMeshActor->GetSourceActors().Contains(DraggedObjects.StaticMeshActors.GetValue()[ActorIndex]))
					{
						bContaining = true;
						break;
					}
				}

				if (!bContaining)
				{
					if (DraggedObjects.StaticMeshActors->Num() > 1)
					{

						return FDragValidationInfo(EStandInsOutlinerActionType::MoveActorToStandInMeshActor, FStandInsOutlinerDragDropOp::ToolTip_MultipleSelection_Compatible, LOCTEXT("MoveMultipleToStandInMeshActor", "Move Actors to Stand-In Mesh Actor"));
					}
					else
					{
						return FDragValidationInfo(EStandInsOutlinerActionType::MoveActorToStandInMeshActor, FStandInsOutlinerDragDropOp::ToolTip_Compatible, LOCTEXT("MoveToStandInMeshActor", "Move Actor to Stand-In Mesh Actor"));
					}
				}
				else
				{
					return FDragValidationInfo(EStandInsOutlinerActionType::InvalidAction, FStandInsOutlinerDragDropOp::ToolTip_Incompatible, LOCTEXT("AlreadyPartOStandInSubActors", "Cannot add to existing Stand-In Mesh Actor"));
				}
			}
			else
			{
				TArray<AActor*> Actors;
				for (auto StaticMeshActor : DraggedObjects.StaticMeshActors.GetValue())
				{
					Actors.Add(StaticMeshActor.Get());
				}

				Actors.Add(StandInMeshActor.Get());

				const bool MultipleActors = DraggedObjects.StaticMeshActors->Num() > 1;


				return FDragValidationInfo(EStandInsOutlinerActionType::AddActorToStandInMeshActor,
					MultipleActors ? FStandInsOutlinerDragDropOp::ToolTip_MultipleSelection_Compatible : FStandInsOutlinerDragDropOp::ToolTip_Compatible,
					MultipleActors ? LOCTEXT("AddMultipleToStandInMeshActor", "Add Actors to Stand-In Mesh Actor") : LOCTEXT("AddToStandInActor", "Add Actor to Stand-In Mesh Actor"));

			}

		}
		
		if (DraggedObjects.bSceneOutliner)
		{
			return FDragValidationInfo(EStandInsOutlinerActionType::InvalidAction, FStandInsOutlinerDragDropOp::ToolTip_Incompatible, LOCTEXT("AlreadyPartOStandInSubActors", "Actor is already Stand-In Mesh Actor"));
		}
	}
	else if (DraggedObjects.StandinLODActors.IsSet() && DraggedObjects.StandinLODActors->Num() > 0)
	{
		if (!DraggedObjects.bSceneOutliner)
		{
			bool bValidForMerge = true;

			UObject* LevelOuter = nullptr;
			UObject* SubActorOuter = (StandInMeshActor->GetSourceActors().Num()) ? StandInMeshActor->GetSourceActors()[0]->GetOuter() : nullptr;
			
			for (auto Actor : DraggedObjects.StandinLODActors.GetValue())
			{
				AStandInMeshActor* InReplacementLODActor = Cast<AStandInMeshActor>(Actor.Get());

				if (InReplacementLODActor)
				{
					if (InReplacementLODActor == StandInMeshActor.Get() || StandInMeshActor->GetSourceActors().Contains(InReplacementLODActor))
					{
						bValidForMerge = false;
					}

					if (LevelOuter == nullptr)
					{
						LevelOuter = StandInMeshActor->GetOuter();
					}
					else if (LevelOuter != StandInMeshActor->GetOuter())
					{
						bValidForMerge = false;
					}

					if (InReplacementLODActor->GetSourceActors().Num() && SubActorOuter != InReplacementLODActor->GetSourceActors()[0]->GetOuter())
					{
						bValidForMerge = false;
					}
				}		
			}

			if (bValidForMerge)
			{
				return FDragValidationInfo(EStandInsOutlinerActionType::MergeStandInMeshActors, (DraggedObjects.StandinLODActors->Num() == 1) ? FStandInsOutlinerDragDropOp::ToolTip_Compatible : FStandInsOutlinerDragDropOp::ToolTip_MultipleSelection_Compatible, LOCTEXT("MergeReplacementLODActors", "Merge Replacement Actors(s)"));
			}
			else
			{
				return FDragValidationInfo(EStandInsOutlinerActionType::InvalidAction, FStandInsOutlinerDragDropOp::ToolTip_Incompatible , LOCTEXT("InvalidOperation", "Invalid Operation"));
			}
		}
	}


	return FDragValidationInfo(EStandInsOutlinerActionType::InvalidAction, FStandInsOutlinerDragDropOp::ToolTip_Incompatible, LOCTEXT("NotImplemented", "Not implemented"));
}

void StandInsOutliner::FStandInMeshActorDropTarget::OnDrop(FDragDropPayload& DraggedObjects, const FDragValidationInfo& ValidationInfo, TSharedRef<SWidget> DroppedOnWidget)
{
	AActor* DropActor = StandInMeshActor.Get();
	if (!DropActor)
	{
		return;
	}


	auto& DraggedStaticMeshActors = DraggedObjects.StaticMeshActors.GetValue();
	auto& DraggedLODActors = DraggedObjects.StandinLODActors.GetValue();
	if (ValidationInfo.ActionType == EStandInsOutlinerActionType::AddActorToStandInMeshActor || ValidationInfo.ActionType == EStandInsOutlinerActionType::MoveActorToStandInMeshActor)
	{
		for (int32 ActorIndex = 0; ActorIndex < DraggedStaticMeshActors.Num(); ++ActorIndex)
		{
			auto Actor = DraggedStaticMeshActors[ActorIndex];
			
			IStandinUtilitiesModule::Get().AddActorToStandinActor(Actor.Get(), StandInMeshActor.Get());
		}
	}
	else if (ValidationInfo.ActionType == EStandInsOutlinerActionType::MergeStandInMeshActors)
	{
		for (int32 ActorIndex = 0; ActorIndex < DraggedLODActors.Num(); ++ActorIndex)
		{
			AStandInMeshActor* InLODActor = Cast<AStandInMeshActor>(DraggedLODActors[ActorIndex].Get());
			IStandinUtilitiesModule::Get().MergeStandinActors(StandInMeshActor.Get(), InLODActor);
		}
	}
}

ECheckBoxState StandInsOutliner::FStandInMeshActorItem::IsGameStandin() const
{
	if (StandInMeshActor.IsValid())
	{
		AStandInMeshActor* StandinActor = StandInMeshActor.Get();
		return StandinActor->IsGameStandin() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
	}
	return ECheckBoxState::Unchecked;
}

void StandInsOutliner::FStandInMeshActorItem::OnGameStandinChanged(ECheckBoxState InState)
{
	if (StandInMeshActor.IsValid())
	{
		AStandInMeshActor* StandinActor = StandInMeshActor.Get();
		StandinActor->UseAsGameStandin(InState == ECheckBoxState::Checked);
	}
}

bool StandInsOutliner::FStandInMeshActorItem::IsHiddenInEditor() const
{
	if (StandInMeshActor.IsValid())
	{
		return StandInMeshActor->IsTemporarilyHiddenInEditor();
	}

	return false;
}

void StandInsOutliner::FStandInMeshActorItem::SetIsVisibleInEditor(bool bIsVisible)
{
	if (StandInMeshActor.IsValid())
	{
		StandInMeshActor->SetIsVisibleInEditor(bIsVisible, !bIsVisible);
	}
	 
}

void StandInsOutliner::FStandInMeshActorItem::SetHiddenInEditor(bool bHidden)
{
	if (StandInMeshActor.IsValid())
	{
		StandInMeshActor->HiddenInEditor(bHidden);
	}
}

#undef LOCTEXT_NAMESPACE
